#include <iostream>
#include <fstream>
#include <cstring>

#include "Misc.h"
#include "EbFuncs.h"
#include "Rom.h"

using namespace std;

Rom::Rom() {
  romBuffer = 0;
  initBitrevs();
}

Rom::~Rom() {
  if (romBuffer)
    delete[] romBuffer;
}

void Rom::open(char* fname) {
  ifstream f;
  int i;

  f.open(fname, ios::binary);

  // Record the size of the ROM in bytes
  f.seekg(0, ios::end);
  size = f.tellg();
  f.seekg(0, ios::beg);

  // Allocate space for rom to use in memory
  romBuffer = new uchar [size];

  // Read ROM into memory
  f.read((char*) romBuffer, size);

  f.close();

  for (i = 0; i < size; i++)
    romBuffer[i] = romBuffer[i] & 0xff;

   lastFreePos = size-1;
   firstFreePos = 0x300200;
}

void Rom::openExpand(char* fname) {
  ifstream f;
  int i;

  f.open(fname, ios::binary);

  // Record the size of the ROM in bytes
  f.seekg(0, ios::end);
  size = f.tellg();
  f.seekg(0, ios::beg);

  // Allocate space for rom to use in memory
  // and read Rom file into memory
  romBuffer = new uchar [0x400200];
  f.read((char*) romBuffer, size);
  f.close();
  for (i = 0; i < size; i++)
    romBuffer[i] = romBuffer[i] & 0xff;

  // Expand ROM
  if (size == 0x300200) {
    // Expand to 4MB
/*    for (i=0; i<(0x400200-0x300200); i++) {
      romBuffer[0x300200+i] = ( i % 0x100 == 0xff ? 2 : 0 );
    }*/
    for (i=0x300200; i<0x400200; i++)
      romBuffer[i]=0;
    size = 0x400200;
  }

  lastFreePos = size-1;
  firstFreePos = 0x300200;
}


void Rom::save(char* fname) {
  ofstream f;
 
  f.open(fname, ios::binary);
  f.write((char*) romBuffer, size);
  f.close();
}

void Rom::close() {
  delete[] romBuffer;
  romBuffer = 0;
}

void Rom::seek(uint addr) {
  seekAddr = addr;
}

uint Rom::tell() {
  return seekAddr;
}

uchar Rom::read() {
  return read(seekAddr++);
}

uchar Rom::read(uint addr) {
  if (addr > size) {
    cerr << "Error: reading out of range in Rom::read()" << endl;
    return 0;
  }
  return romBuffer[addr];
}

void Rom::read(uchar* buf, int len) {
  read(seekAddr, buf, len);
  seekAddr += len;
}

void Rom::read(uint addr, uchar* buf, int len) {
  if (addr + len - 1 > size) {
    cerr << "Error: reading out of range in Rom::read()" << endl;
    return;
  }
  int i;
  for (i = 0; i < len; i++)
    buf[i] = romBuffer[addr+i];
}

// Reads in reverse endian format
uint Rom::readMulti(uint addr, uint len) {
  uint out = 0, i, mySeek = addr;
  for (i = 0; i < len; i++) {
    out += read(mySeek++) << (8*i);
  }
  return out;
}

uint Rom::readMulti(uint len) {
  uint out = 0, i;
  for (i = 0; i < len; i++) {
    out += read(seekAddr++) << (8*i);
  }
  return out;
}


void Rom::write(uchar byte) {
  write(byte, seekAddr++);
}

void Rom::writeMulti(uint data, uint len) {
  int i;
  for (i=0; i < len; i++) {
    write((uchar) ((data >> (8*i)) & 0xff));
  }
}

void Rom::writeMulti(uint data, uint addr, uint len) {
  int i;
  for (i=0; i < len; i++)
    write((uchar) ((data >> (8*i)) & 0xff), addr++);
}

void Rom::write(uchar byte, uint addr) {
  if (addr > size)
    cerr << "Error: writing out of range in Rom::write()" << endl;
  else
    romBuffer[addr] = byte;
}

void Rom::write(uchar *data, uint addr, uint len) {
  memcpy(&romBuffer[addr], data, len*sizeof(uchar));
}

uchar* Rom::getBuffer() {
  return romBuffer;
}

uint Rom::getFreeRange(int len) {
  if (lastFreePos - (len-1) < firstFreePos) {
    cerr << "Error: findFreeRange: No free range " << len << " bytes long found." << endl;
    return 0;
  }

  lastFreePos = lastFreePos-len;
  return lastFreePos + 1;
}

uint Rom::getLastFreePos() {
  return lastFreePos;
}

void Rom::setFirstFreePos(uint ffp) {
  firstFreePos = ffp;
}

// Comp/decomp code
// Adapted from Goplat's Tile Editor
void Rom::initBitrevs() {
  uchar x;
  int i;
  for (i=0; i<256; i++) {
    x = (uchar) i;
    x = (((x >> 1) & 0x55) | ((x << 1) & 0xAA));
    x = (((x >> 2) & 0x33) | ((x << 2) & 0xCC));
    bitrevs[i] = (((x >> 4) & 0x0F) | ((x << 4) & 0xF0));
  }
}

// Used internally by comp
void Rom::encode(uchar **bpos, int length, int type) {
  if(length > 32) {
    *(*bpos)++ = 0xE0 + 4 * type + ((length - 1) >> 8);
    *(*bpos)++ = (length - 1) & 0xFF;
  } else
    *(*bpos)++ = 0x20 * type + length - 1;
}

void Rom::rencode(uchar **bpos, uchar *pos, int length) {
  if(length <= 0)
    return;
  encode(bpos, length, 0);
  memcpy(*bpos, pos, length);
  *bpos += length;
}

/* The compressor function.
 * Takes a pointer to the uncompressed block, a pointer to 65536 bytes
 * which it compresses into, and the size of the uncompressed block.
 * Returns the number of bytes compressed.
 */
int Rom::comp(uchar *udata, uchar *buffer, int length) {
        uchar *bpos = buffer, *limit = &udata[length];
        uchar *pos = udata, *pos2, *pos3, *pos4;
        int tmp;
        /*static uchar bitrevs[256];
        for(tmp = 0; tmp < 256; tmp++) {
                uchar x = tmp;
                x = ((x >> 1) & 0x55) | ((x << 1) & 0xAA);
                x = ((x >> 2) & 0x33) | ((x << 2) & 0xCC);
                bitrevs[tmp] = ((x >> 4) & 0x0F) | ((x << 4) & 0xF0);
        }*/
        while(pos < limit) {
                /*printf("%d\t%d\n", pos - udata, bpos - buffer);*/
                /* Look for patterns */
                for(pos2 = pos; pos2 < limit && pos2 < pos + 1024; pos2++) {
                        for(pos3 = pos2; pos3 < limit && pos3 < pos2 + 1024 && *pos2 == *pos3; pos3++);
                        if(pos3 - pos2 >= 3) {
                                rencode(&bpos, pos, pos2 - pos);
                                encode(&bpos, pos3 - pos2, 1);
                                *bpos++ = *pos2;
                                pos = pos3;
                                break;
                        }
                        for(pos3 = pos2; pos3 < limit && pos3 < pos2 + 2048 && *pos3 == *pos2 && pos3[1] == pos2[1]; pos3 += 2);
                        if(pos3 - pos2 >= 6) {
                                rencode(&bpos, pos, pos2 - pos);
                                encode(&bpos, (pos3 - pos2) / 2, 2);
                                *bpos++ = pos2[0];
                                *bpos++ = pos2[1];
                                pos = pos3;
                                break;
                        }
                        for(tmp = 0, pos3 = pos2; pos3 < limit && pos3 < pos2 + 1024 && *pos3 == *pos2 + tmp; pos3++, tmp++);
                        if(pos3 - pos2 >= 4) {
                                rencode(&bpos, pos, pos2 - pos);
                                encode(&bpos, pos3 - pos2, 3);
                                *bpos++ = *pos2;
                                pos = pos3;
                                break;
                        }
                        for(pos3 = udata; pos3 < pos2; pos3++) {
                                for(tmp = 0, pos4 = pos3; pos4 < pos2 && tmp < 1024 && *pos4 == pos2[tmp]; pos4++, tmp++);
                            if(tmp >= 5) {
                                        rencode(&bpos, pos, pos2 - pos);
                                        encode(&bpos, tmp, 4);
                                        *bpos++ = (pos3 - udata) >> 8;
                                        *bpos++ = (pos3 - udata) & 0xFF;
                                        pos = pos2 + tmp;
                                        goto DONE;
                                }
                                for(tmp = 0, pos4 = pos3; pos4 < pos2 && tmp < 1024 && *pos4 == bitrevs[pos2[tmp]]; pos4++, tmp++);
                                if(tmp >= 5) {
                                        rencode(&bpos, pos, pos2 - pos);
                                        encode(&bpos, tmp, 5);
                                        *bpos++ = (pos3 - udata) >> 8;
                                        *bpos++ = (pos3 - udata) & 0xFF;
                                        pos = pos2 + tmp;
                                        goto DONE;
                                }
                                for(tmp = 0, pos4 = pos3; pos4 >= udata && tmp < 1024 && *pos4 == pos2[tmp]; pos4--, tmp++);
                                if(tmp >= 5) {
                                        rencode(&bpos, pos, pos2 - pos);
                                        encode(&bpos, tmp, 6);
                                        *bpos++ = (pos3 - udata) >> 8;
                                        *bpos++ = (pos3 - udata) & 0xFF;
                                        pos = pos2 + tmp;
                                        goto DONE;
                                }
                        }
                }
                DONE:
                /* Can't compress, so just use 0 (raw) */
                rencode(&bpos, pos, pos2 - pos);
                if(pos < pos2) pos = pos2;
        }
        *bpos++ = 0xFF;
        return bpos - buffer;
}

/* The decompressor function.
 * Takes a pointer to the compressed block, a pointer to the buffer
 * which it decompresses into, . Returns the number of bytes uncompressed,
 * or -1 if decompression failed.
 */
int Rom::decomp(uint addr, uchar *buffer, int maxlen) {
	uchar *cdata = &romBuffer[addr];
        uchar *bpos = buffer, *bpos2, tmp;
        while(*cdata != 0xFF) {
                int cmdtype = *cdata >> 5;
                int len = (*cdata & 0x1F) + 1;
                if(cmdtype == 7) {
                        cmdtype = (*cdata & 0x1C) >> 2;
                        len = ((*cdata & 3) << 8) + *(cdata + 1) + 1;
                        cdata++;
                }
                if(bpos + len > &buffer[maxlen]) return -1;
                cdata++;
                if(cmdtype >= 4) {
                        bpos2 = &buffer[(*cdata << 8) + *(cdata + 1)];
                        if(bpos2 >= &buffer[maxlen]) return -1;
                        cdata += 2;
                }
                switch(cmdtype) {
                        case 0:
                                memcpy(bpos, cdata, len);
                                cdata += len;
                                bpos += len;
                                break;
                        case 1:
                                memset(bpos, *cdata++, len);
                                bpos += len;
                                break;
                        case 2:
                                if(bpos + 2 * len > &buffer[maxlen]) return -1;
                                while(len--) {
                                        *(short *)bpos = *(short *)cdata;
                                        bpos += 2;
                                }
                                cdata += 2;
                                break;
                        case 3:
                                tmp = *cdata++;
                                while(len--) *bpos++ = tmp++;
                                break;
                        case 4:
                                if(bpos2 + len > &buffer[maxlen]) return -1;
                                memcpy(bpos, bpos2, len);
                                bpos += len;
                                break;
                        case 5:
                                if(bpos2 + len > &buffer[maxlen]) return -1;
                                while(len--) {
                                        tmp = *bpos2++;
                                        /* reverse the bits */
                                        tmp = ((tmp >> 1) & 0x55) | ((tmp << 1) & 0xAA);
                                        tmp = ((tmp >> 2) & 0x33) | ((tmp << 2) & 0xCC);
                                        tmp = ((tmp >> 4) & 0x0F) | ((tmp << 4) & 0xF0);
                                        *bpos++ = tmp;
                                }
                                break;
                        case 6:
                                if(bpos2 - len + 1 < buffer) return -1;
                                while(len--) *bpos++ = *bpos2--;
                                break;
                        case 7:
                                return -1;
                }
        }
        return bpos - buffer;
}

